package net.betou.common.web.cos;

import java.io.IOException;

import javax.servlet.ServletInputStream;

/**
 * A <code>BufferedServletInputStream</code> wraps a
 * <code>ServletInputStream</code> in order to provide input buffering and to
 * avoid calling the the <code>readLine</code> method of the wrapped
 * <code>ServletInputStream</code>.
 * <p>
 * This is necessary because some servlet containers rely on the default
 * implementation of the <code>readLine</code> method provided by the Servlet
 * API classes, which is very slow. Tomcat 3.2, Tomcat 3.1, the JSWDK 1.0 web
 * server and the JSDK2.1 web server are all known to need this class for
 * performance reasons.
 * <p>
 * Also, it may be used to work around a bug in the Servlet API 2.0
 * implementation of <code>readLine</code> which contains a bug that causes
 * <code>ArrayIndexOutOfBoundsExceptions</code> under certain conditions. Apache
 * JServ is known to suffer from this bug.
 * 
 * @author Geoff Soutter
 * @version 1.1, 2001/05/21, removed block of commented out code
 * @version 1.0, 2000/10/27, initial revision
 */
public class BufferedServletInputStream extends ServletInputStream{

	/** input stream we are filtering */
	private ServletInputStream in;

	/** our buffer */
	private byte [ ] buf = new byte [ 64 * 1024 ]; // 64k

	/** number of bytes we've read into the buffer */
	private int count;

	/** current position in the buffer */
	private int pos;

	/**
	 * Creates a <code>BufferedServletInputStream</code> that wraps the provided
	 * <code>ServletInputStream</code>.
	 * 
	 * @param in
	 *            a servlet input stream.
	 */
	public BufferedServletInputStream( ServletInputStream in ) {
		this.in = in;
	}

	/**
	 * Fill up our buffer from the underlying input stream. Users of this method
	 * must ensure that they use all characters in the buffer before calling
	 * this method.
	 * 
	 * @exception IOException
	 *                if an I/O error occurs.
	 */
	private void fill( ) throws IOException {
		int i = in.read( buf, 0, buf.length );
		if( i > 0 ){
			pos = 0;
			count = i;
		}
	}

	/**
	 * Implement buffering on top of the <code>readLine</code> method of the
	 * wrapped <code>ServletInputStream</code>.
	 * 
	 * @param b
	 *            an array of bytes into which data is read.
	 * @param off
	 *            an integer specifying the character at which this method
	 *            begins reading.
	 * @param len
	 *            an integer specifying the maximum number of bytes to read.
	 * @return an integer specifying the actual number of bytes read, or -1 if
	 *         the end of the stream is reached.
	 * @exception IOException
	 *                if an I/O error occurs.
	 */
	public int readLine( byte b[], int off, int len ) throws IOException {
		int total = 0;
		if( len == 0 ){
			return 0;
		}

		int avail = count - pos;
		if( avail <= 0 ){
			fill( );
			avail = count - pos;
			if( avail <= 0 ){
				return -1;
			}
		}
		int copy = Math.min( len, avail );
		int eol = findeol( buf, pos, copy );
		if( eol != -1 ){
			copy = eol;
		}
		System.arraycopy( buf, pos, b, off, copy );
		pos += copy;
		total += copy;

		while( total < len && eol == -1 ){
			fill( );
			avail = count - pos;
			if( avail <= 0 ){
				return total;
			}
			copy = Math.min( len - total, avail );
			eol = findeol( buf, pos, copy );
			if( eol != -1 ){
				copy = eol;
			}
			System.arraycopy( buf, pos, b, off + total, copy );
			pos += copy;
			total += copy;
		}
		return total;
	}

	/**
	 * Attempt to find the '\n' end of line marker as defined in the comment of
	 * the <code>readLine</code> method of <code>ServletInputStream</code>.
	 * 
	 * @param b
	 *            byte array to search.
	 * @param pos
	 *            position in byte array to search from.
	 * @param len
	 *            maximum number of bytes to search.
	 * 
	 * @return the number of bytes including the \n, or -1 if none found.
	 */
	private static int findeol( byte b[], int pos, int len ) {
		int end = pos + len;
		int i = pos;
		while( i < end ){
			if( b[ i++ ] == '\n' ){
				return i - pos;
			}
		}
		return -1;
	}

	/**
	 * Implement buffering on top of the <code>read</code> method of the wrapped
	 * <code>ServletInputStream</code>.
	 * 
	 * @return the next byte of data, or <code>-1</code> if the end of the
	 *         stream is reached.
	 * @exception IOException
	 *                if an I/O error occurs.
	 */
	public int read( ) throws IOException {
		if( count <= pos ){
			fill( );
			if( count <= pos ){
				return -1;
			}
		}
		return buf[ pos++ ] & 0xff;
	}

	/**
	 * Implement buffering on top of the <code>read</code> method of the wrapped
	 * <code>ServletInputStream</code>.
	 * 
	 * @param b
	 *            the buffer into which the data is read.
	 * @param off
	 *            the start offset of the data.
	 * @param len
	 *            the maximum number of bytes read.
	 * @return the total number of bytes read into the buffer, or
	 *         <code>-1</code> if there is no more data because the end of the
	 *         stream has been reached.
	 * @exception IOException
	 *                if an I/O error occurs.
	 */
	public int read( byte b[], int off, int len ) throws IOException {
		int total = 0;
		while( total < len ){
			int avail = count - pos;
			if( avail <= 0 ){
				fill( );
				avail = count - pos;
				if( avail <= 0 ){
					if( total > 0 )
						return total;
					else
						return -1;
				}
			}
			int copy = Math.min( len - total, avail );
			System.arraycopy( buf, pos, b, off + total, copy );
			pos += copy;
			total += copy;
		}
		return total;
	}
}
